// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: xguo

#include "coin2/exchange/mexc_futures/order_v2/parser.h"

#include <map>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "coin2/base/conversion.h"
#include "coin2/base/log.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::mexc_futures::order_v1 {

namespace {

using coin::proto::AccountBalance;
using coin::proto::AccountPosition;
using coin::proto::CancelOrderResponse;
using coin::proto::CurrencyTransferElement;
using coin::proto::FillType;
using coin::proto::ProductFillBundle;
using coin::proto::ProductOrderBundle;
using coin::proto::ProductOrderElement;
using coin::proto::SubmitOrderResponse;
using coin::proto::TransferRequestProto;
using coin2::exchange::base::order::IOrderContext;
using coin2::exchange::mexc_futures::symbology::MexcFuturesProduct;

using JsonValue = rapidjson::GenericValue<rapidjson::UTF8<>>;
using TransferType = coin::proto::TransferRequestProto::TransferType;

inline int64_t GetProcOrderId(const JsonValue& data) {
  int64_t proc_order_id = 0;
  if (data.HasMember("externalOid")) {
    std::string proc_str = data["externalOid"].GetString();
    try {
      proc_order_id = stoll(proc_str);
    } catch (std::exception& e) {
      LOG(ERROR) << e.what() << proc_str;
    }
  }
  return proc_order_id;
}

inline coin::proto::OrderSide GetOrderSide(char c) {
  using OrderSide = coin::proto::OrderSide;

  if (c == '1') {
    return OrderSide::BUY_OPEN_ORDER;
  } else if (c == '2') {
    return OrderSide::BUY_CLOSE_ORDER;
  } else if (c == '3') {
    return OrderSide::SELL_OPEN_ORDER;
  } else if (c == '4') {
    return OrderSide::SELL_CLOSE_ORDER;
  } else {
    return OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

inline coin::proto::OrderType GetOrderType(char c) {
  using OrderType = coin::proto::OrderType;

  if (c == '1') {
    return OrderType::LIMIT_ORDER;
  } else if (c == '2') {
    return OrderType::LIMIT_ORDER;
  } else if (c == '3') {
    return OrderType::IOC_LIMIT_ORDER;
  } else if (c == '4') {
    return OrderType::FOK_LIMIT_ORDER;
  } else if (c == '5') {
    return OrderType::MARKET_ORDER;
  } else {
    return OrderType::UNKNOWN_ORDER_TYPE;
  }
}

void SetOrderStatus(const char status, ProductOrderElement* order) {
  if (status == '1' || status == '2') {
    order->set_is_live(true);
  } else if (status == '5') {
    order->set_is_live(false);
  } else if (status == '4') {
    order->set_cancel_confirmed(true);
  } else if (status == '3') {
    order->set_fully_filled(true);
  } else {
    LOG(ERROR) << "[ParseRestOrderInfo] Unknown order status! " << status;
  }
}

#if 0
bool IsAccountUpdate(const rapidjson::GenericValue<rapidjson::UTF8<>>& json) {
  return json.HasMember("e") && std::string_view(json["e"].GetString()) == "ACCOUNT_UPDATE";
}

bool IsOrderUpdate(const rapidjson::GenericValue<rapidjson::UTF8<>>& json) {
  return json.HasMember("e") && std::string_view(json["e"].GetString()) == "ORDER_TRADE_UPDATE";
}
#endif

}  // namespace

void MexcFuturesParser::ParseRestAccountBalance() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_balance = ParseBalance(document);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

bool MexcFuturesParser::ParseRawBalance(const JsonValue& doc, RawBalanceMap* raw_balance_map) {
  if (!doc["success"].GetBool()) {
    LOG(ERROR) << "[ParseBalance] " << JsonToString(doc);
    return false;
  }

  for (const auto& data : doc["data"].GetArray()) {
    double hold = stod(data["frozenBalance"].GetString());
    double avail = stod(data["availableBalance"].GetString());
    double total = avail + hold;
    CurrencyBalance balance;
    balance.set_currency_native(data["currency"].GetString());
    balance.set_available(avail);
    balance.set_hold(hold);
    balance.set_total(total);
    raw_balance_map->emplace(balance.currency_native(), balance);
  }

  return true;
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountBalance MexcFuturesParser::ParseBalance(const JsonValue& doc) {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }

  AccountBalance acc_balance;
  acc_balance.set_exchange("Mexc");
  acc_balance.set_market_type("Futures");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  return acc_balance;
}

void MexcFuturesParser::ParseRestAccountPosition() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestAccountPosition] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_position = ParsePosition(doc);
  if (!account_position.has_exchange() || !account_position.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  result_.set_type(MsgType::ACCOUNT_POSITION);
  *result_.mutable_account_position() = std::move(account_position);
}

bool MexcFuturesParser::ParseRawPosition(const JsonValue& doc, RawPositionMap* raw_position_map) {
  if (!doc.IsArray()) {
    LOG(ERROR) << "[ParsePosition] " << JsonToString(doc);
    return false;
  }

  for (const auto& data : doc.GetArray()) {
    if (!data.HasMember("symbol") || !data.HasMember("positionAmt") ||
        !data.HasMember("entryPrice")) {
      LOG(ERROR) << "Unexpected position data! " << JsonToString(data);
      continue;
    }
    auto native_symbol = data["symbol"].GetString();
    auto& position = (*raw_position_map)[native_symbol];
    double pos_amt = stod(data["positionAmt"].GetString());
    if (pos_amt >= 0.) {
      position.set_long_position(pos_amt);
    } else {
      position.set_short_position(-pos_amt);
    }
    position.set_net_position(pos_amt);
  }

  return true;
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountPosition MexcFuturesParser::ParsePosition(const JsonValue& doc) {
  RawPositionMap raw_position_map;
  auto success = ParseRawPosition(doc, &raw_position_map);
  if (!success) {
    return {};
  }

  AccountPosition acc_position;
  acc_position.set_exchange("Mexc");
  acc_position.set_market_type("Futures");
  for (const auto& native_symbol : symcache_->GetProductNative()) {
    if (raw_position_map.count(native_symbol) != 0) {
      continue;
    }
    auto* position = acc_position.add_each_position();
    position->set_long_position(0);
    position->set_short_position(0);
    position->set_net_position(0);
    position->set_available_long_position(0);
    position->set_available_short_position(0);
    position->set_native_symbol(native_symbol);

    DLOG(INFO) << "POS: " << native_symbol << " " << position->net_position();
  }

  for (auto& pair : raw_position_map) {
    auto* position = acc_position.add_each_position();
    *position = std::move(pair.second);
    position->set_native_symbol(pair.first);
    DLOG(INFO) << "POS: " << pair.first << " " << position->net_position();
  }
  is_account_position_initialized_ = true;

  return acc_position;
}

#if 0
bool MexcFuturesParser::ParseRawPositionFromWs(
    const JsonValue& doc,
    RawPositionMap* raw_position_map) {

  // document.Parse<rapidjson::kParseNumbersAsStringsFlag>(packet.payload.data());
  if (!(doc.HasMember("E"))) {
    LOG(ERROR) << JsonToString(doc);
    return false;
  }
  if (!(doc.HasMember("a") && doc["a"].IsObject())) {
    LOG(ERROR) << JsonToString(doc);
    return false;
  }
  if (!(doc["a"].HasMember("P") && doc["a"]["P"].IsArray())) {
    LOG(ERROR) << JsonToString(doc);
    return false;
  }

  const auto& position_list = doc["a"]["P"];
  for (const auto& product_pos : position_list.GetArray()) {
    if (std::string_view(product_pos["ps"].GetString()) != "BOTH") {
      LOG(ERROR) << "Ws position ps not BOTH: " << JsonToString(doc);
      continue;
    }
    auto native_symbol = product_pos["s"].GetString();
    if (raw_position_map->count(native_symbol) == 0) {
      raw_position_map->emplace(native_symbol, ProductPosition{});
    }
    auto& position = raw_position_map->at(native_symbol);
    auto net_pos = stod(product_pos["pa"].GetString());
    position.set_net_position(net_pos);
    if (net_pos >= 0) {
      position.set_long_position(net_pos);
    } else {
      position.set_short_position(-net_pos);
    }
  }

  return true;
}

bool MexcFuturesParser::ParseWsPosition(const JsonValue& doc, AccountPosition* acc_position) {
  acc_position->Clear();

  RawPositionMap raw_position_map;
  bool success = ParseRawPositionFromWs(doc, &raw_position_map);
  if (!success) {
    return false;
  }

  acc_position->set_exchange("Mexc");
  acc_position->set_market_type("Futures");
  for (const auto& native_symbol : symcache_->GetProductNative()) {
    if (raw_position_map.count(native_symbol) == 0) {
      continue;
    }
    auto* position = acc_position->add_each_position();
    position->MergeFrom(raw_position_map.at(native_symbol));
    auto* product = symcache_->GetProductByNative(native_symbol);
    position->set_symbol(product->symbol());
    position->set_native_symbol(product->order_native_symbol());
    DLOG(INFO) << "POS: " << product->symbol() << " " << position->net_position();
  }

  return true;
}
#endif

ProductOrderElement MexcFuturesParser::ParseRestOrderInfo(const JsonValue& data) {
  ProductOrderElement order;
  std::string exchange_id = std::string(data["orderId"].GetString());
  int64_t order_timestamp_ft = msg_->timestamp;

  order.set_market_type(MarketType::Futures);
  order.set_exchange_type(ExchangeType::Mexc);
  order.set_api_version("v1");
  order.set_exchange_order_id(exchange_id);
  order.set_symbol(data["symbol"].GetString());
  order.set_price(stod(data["price"].GetString()));
  order.set_qty(stod(data["vol"].GetString()));
  order.set_proc_order_id(GetProcOrderId(data));
  order.set_side(GetOrderSide(data["side"].GetString()[0]));
  order.set_order_type(GetOrderType(data["type"].GetString()[0]));
  order.set_timestamp(order_timestamp_ft);
  SetOrderStatus(data["state"].GetString()[0], &order);
  return order;
}

void MexcFuturesParser::ParseRestOpenOrders() { ParseRestOrderInfoList(); }

void MexcFuturesParser::ParseRestAllOrders() { ParseRestOrderInfoList(); }

void MexcFuturesParser::ParseRestOrderInfoList() {
  JsonDocument data;
  if (!PrepareJsonDocument(&data)) {
    LOG(ERROR) << "[ParseRestOpenOrders] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (data.IsObject() && !data["success"].GetBool()) {
    LOG(ERROR) << "[ParseRestOrderInfoList] " << JsonToString(data);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& info : data["data"].GetArray()) {
    auto new_order = ParseRestOrderInfo(info);

    // Ignore open orders form QueryHistoricalOrder response
    // only trust open orders from QueryOpenOrders response.
    if (!(new_order.is_live()) && new_order.has_market_type()) {
      auto* order = product_order_bundle->add_each_order();
      order->MergeFrom(new_order);
      product_order_bundle_found = true;
    }
  }
  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

ProductOrderBundle MexcFuturesParser::ParseWsOrderInfoList(const JsonValue& order_data) {
  ProductOrderBundle product_order_bundle;
  (void)order_data;
#if 0
  if (!order_data.IsObject()) {
    LOG(ERROR) << "Unexpected json format! " << JsonToString(order_data);
    return product_order_bundle;
  }

  if (!order_data.HasMember("o")) {
    LOG(ERROR) << "Unexpected json format! " << JsonToString(order_data);
    return product_order_bundle;
  }

  const auto& data = order_data["o"];
  auto* order = product_order_bundle.add_each_order();
  order->set_market_type(MarketType::Futures);
  order->set_exchange_type(ExchangeType::Mexc);
  order->set_api_version("v1");

  order->set_exchange_order_id(data["s"].GetString() + std::string(data["i"].GetString()));
  order->set_symbol(data["s"].GetString());
  order->set_price(stod(data["p"].GetString()));
  order->set_qty(stod(data["q"].GetString()));
  order->set_proc_order_id(stoll(data["c"].GetString()));
  order->set_side(GetOrderSide(data["S"].GetString()));
  order->set_order_type(GetOrderType(data["o"].GetString()));
  SetOrderStatus(data["X"].GetString(), order);
  order->set_timestamp(msg_->timestamp);
#endif
  return product_order_bundle;
}

ProductFillBundle MexcFuturesParser::ParseWsFillInfoList(const JsonValue& order_data) {
  ProductFillBundle product_fill_bundle;
  (void)order_data;
#if 0
  if (!order_data.HasMember("o")) {
    LOG(ERROR) << "Unexpected json format! " << JsonToString(order_data);
    return product_fill_bundle;
  }

  const auto& data = order_data["o"];
  std::string_view status = data["X"].GetString();
  if (status == "FILLED" || status == "PARTIALLY_FILLED" || status == "TRADE") {
    auto* fill = product_fill_bundle.add_each_fill();
    fill->set_symbol(data["s"].GetString());
    fill->set_fill_timestamp(msg_->timestamp);
    fill->set_exchange_order_id(data["s"].GetString() + std::string(data["i"].GetString()));
    fill->set_proc_order_id(stoll(data["c"].GetString()));
    fill->set_price(stod(data["L"].GetString()));
    fill->set_qty(stod(data["l"].GetString()));
    fill->set_fill_id(data["t"].GetString());
    if (data["m"].GetBool()) {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    }
  }
#endif
  return product_fill_bundle;
}

void MexcFuturesParser::ParseRestFillInfoList() {
  using coin::proto::FillType;

  JsonDocument data;
  if (!PrepareJsonDocument(&data)) {
    LOG(ERROR) << "[ParseRestFillInfoList] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (!data["success"].GetBool()) {
    LOG(ERROR) << "Unexpected rest fill msg!" << JsonToString(data);
    return;
  }

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& info : data["data"].GetArray()) {
    auto* fill = product_fill_bundle->add_each_fill();
    fill->set_symbol(info["symbol"].GetString());
    fill->set_fill_timestamp(msg_->timestamp);
    fill->set_exchange_order_id(std::string(info["orderId"].GetString()));
    fill->set_price(stod(info["price"].GetString()));
    fill->set_qty(stod(info["qty"].GetString()));
    // fill->set_fill_id(info["id"].GetString());
    if (info["isTaker"].GetBool()) {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    } else {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    }
    product_fill_bundle_found = true;
  }
  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void MexcFuturesParser::ParseWsMessage() {
#if 0
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (IsAccountUpdate(document)) {
    // make first rest position result before ws position result,
    // so SetAllZeroPosition won't take effect on partial symbol
    if (!is_account_position_initialized_) {
      result_.set_type(MsgType::IGNORABLE);
      return;
    }
    if (!ParseWsPosition(document, result_.mutable_account_position())) {
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      return;
    }
    // linchuan : ws balance lack of all symbol's unrealized profit, disabled.
    // result_.set_type(MsgType::ACCOUNT_BALANCE);
    result_.set_type(MsgType::ACCOUNT_POSITION);
  } else if (IsOrderUpdate(document)) {
    *result_.mutable_fill_list() = ParseWsFillInfoListV2(document);
    *result_.mutable_order_list() = ParseWsOrderInfoListV2(document);
    result_.set_type(MsgType::ORDER_STATUS);
    result_.set_type(MsgType::ORDER_FILL);
  }
#endif
}

void MexcFuturesParser::ParseSubmitResponse() {
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.base().result_int());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[MexcFuturesSubmitOrder] AsioHttpContextStatus Error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  if (http_context->res.result_int() == 429) {
    LOG(ERROR) << "[MexcFuturesSubmitOrder] API_LIMIT_ERROR";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[MexcFuturesSubmitOrder] Parse Error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (!json.HasMember("data") || !json["success"].GetBool()) {
    LOG(ERROR) << "[MexcFutures Submit Order] Error Response" << http_context->res;
    resp->set_exchange_error_code(json["code"].GetString());
    // resp->set_exchange_error_msg(GetExchangeOrderErrorMsg(json));
    resp->set_success(false);
    return;
  }

  resp->set_success(true);
  resp->set_exchange_order_id(std::string(json["data"].GetString()));
}

void MexcFuturesParser::ParseCancelResponse() {
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.base().result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[MexcFuturesCancelOrder] AsioHttpContextStatus Error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  if (http_context->res.result_int() == 429) {
    LOG(ERROR) << "[MexcFuturesCancelOrder] API_LIMIT_ERROR";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[MexcFuturesCancelOrder] Parse Error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (!json["success"].GetBool()) {
    // resp->set_error_code(); TODO
    resp->set_exchange_error_code(json["data"][0]["errorCode"].GetString());
    resp->set_exchange_error_msg(json["data"][0]["errorMsg"].GetString());
    resp->set_success(false);
    return;
  }

  resp->set_success(true);
  resp->set_cancel_confirmed(true);
}

auto MexcFuturesParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg_->type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    // ParseWsMessage();
    return result_;
  }

  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
      ParseRestOpenOrders();
      break;
    case OrderSubTopicId::REST_ALL_ORDERS:
      ParseRestAllOrders();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseRestFillInfoList();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
      // ParseRestAccountBalance();
      break;
    case OrderSubTopicId::REST_ACCOUNT_POSITION:
      // ParseRestAccountPosition();
      break;
    default: {
      LOG(ERROR) << "Parsing Error. " << msg_->DebugString();
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      break;
    }
  }
  return result_;
}

}  // namespace coin2::exchange::mexc_futures::order_v1
